using System.Globalization;
using System.Net.Http.Json;
using WeatherMcpServer.Data.Entities;

namespace WeatherMcpServer.Services.Weather;

/// <summary>
/// 基于高德(AMap) Web 服务的天气提供器实现。
/// 作为可插拔数据源之一，不改变现有 WeatherTool 行为。
/// </summary>
public sealed class AmapWeatherProvider : IWeatherProvider
{
    private readonly HttpClient _http;
    private readonly string _amapKey;

    /// <summary>
    /// 构造函数，注入 <see cref="IHttpClientFactory"/> 并读取 AMAP_KEY。
    /// </summary>
    public AmapWeatherProvider(IHttpClientFactory httpClientFactory)
    {
        _http = httpClientFactory.CreateClient();
        _amapKey = (Environment.GetEnvironmentVariable("AMAP_KEY") ?? string.Empty).Trim();
    }

    /// <summary>
    /// 获取指定坐标与天偏移的天气概要。
    /// dayOffset: 0=今天, 1=明天, 2=后天。
    /// 返回统一结构，便于多源融合。
    /// </summary>
    /// <param name="latitude">纬度</param>
    /// <param name="longitude">经度</param>
    /// <param name="dayOffset">天偏移（0-2）</param>
    /// <param name="ct">取消令牌</param>
    /// <returns>提供器标准化后的天气概要</returns>
    public async Task<ProviderForecast?> GetForecastAsync(double latitude, double longitude, int dayOffset = 0, CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(_amapKey)) return null;
        if (dayOffset < 0) dayOffset = 0; if (dayOffset > 2) dayOffset = 2;

        var lonStr = Math.Round(longitude, 4).ToString(CultureInfo.InvariantCulture);
        var latStr = Math.Round(latitude, 4).ToString(CultureInfo.InvariantCulture);
        var regeoUrl = $"https://restapi.amap.com/v3/geocode/regeo?location={lonStr},{latStr}&key={_amapKey}";
        var regeoResp = await _http.GetAsync(regeoUrl, ct);
        if (!regeoResp.IsSuccessStatusCode) return null;
        var regeo = await regeoResp.Content.ReadFromJsonAsync<AmapRegeoResponse>(cancellationToken: ct);
        if (regeo is null || regeo.Status != "1") return null;
        var adcode = ExtractAdcode(regeo.RegeoCode);
        if (string.IsNullOrWhiteSpace(adcode)) return null;

        var weatherUrl = $"https://restapi.amap.com/v3/weather/weatherInfo?city={adcode}&extensions=all&key={_amapKey}";
        var weatherResp = await _http.GetAsync(weatherUrl, ct);
        if (!weatherResp.IsSuccessStatusCode) return null;
        var weatherData = await weatherResp.Content.ReadFromJsonAsync<AmapForecastResponse>(cancellationToken: ct);
        if (weatherData is null || weatherData.Status != "1" || weatherData.Forecasts is null || weatherData.Forecasts.Length == 0) return null;

        var f = weatherData.Forecasts[0];
        if (f.Casts is null || f.Casts.Length == 0) return null;
        var idx = Math.Min(dayOffset, Math.Max(0, f.Casts.Length - 1));
        var cast = f.Casts[idx];

        double? dayTemp = TryParseDouble(cast.DayTemp);
        double? nightTemp = TryParseDouble(cast.NightTemp);
        double? tempMax = MaxNullable(dayTemp, nightTemp);
        double? tempMin = MinNullable(dayTemp, nightTemp);

        var inferred = InferIndicatorsFromCast(cast);

        return new ProviderForecast(
            RegionCode: f.Adcode,
            RegionName: f.City,
            TemperatureMax: tempMax,
            TemperatureMin: tempMin,
            RainMm: inferred.RainMm,
            WindLevel: inferred.WindLevel,
            Types: inferred.Types
        );
    }

    // ===== Helpers & Models (AMap) =====
    private static double? TryParseDouble(string? s)
    {
        if (string.IsNullOrWhiteSpace(s)) return null;
        if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var v)) return v;
        if (double.TryParse(s, NumberStyles.Any, CultureInfo.CurrentCulture, out v)) return v;
        var filtered = new string(s.Where(ch => char.IsDigit(ch) || ch == '.' || ch == '-' || ch == '+').ToArray());
        if (double.TryParse(filtered, NumberStyles.Any, CultureInfo.InvariantCulture, out v)) return v;
        return null;
    }

    private static double? MaxNullable(double? a, double? b)
        => (a, b) switch { (null, null) => null, (double x, null) => x, (null, double y) => y, (double x, double y) => Math.Max(x, y) };

    private static double? MinNullable(double? a, double? b)
        => (a, b) switch { (null, null) => null, (double x, null) => x, (null, double y) => y, (double x, double y) => Math.Min(x, y) };

    private static double? ParseWindLevel(string? day, string? night)
    {
        double? pd = ExtractMaxNumber(day);
        double? pn = ExtractMaxNumber(night);
        return MaxNullable(pd, pn);
    }

    private static double? ExtractMaxNumber(string? s)
    {
        if (string.IsNullOrWhiteSpace(s)) return null;
        var nums = new List<double>();
        var buf = new System.Text.StringBuilder();
        void Flush()
        {
            if (buf.Length == 0) return;
            if (double.TryParse(buf.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var v)) nums.Add(v);
            buf.Clear();
        }
        foreach (var ch in s)
        {
            if (char.IsDigit(ch) || ch == '.' || ch == '-') buf.Append(ch); else Flush();
        }
        Flush();
        return nums.Count == 0 ? (double?)null : nums.Max();
    }

    private static (IReadOnlyList<WeatherType> Types, double? RainMm, double? WindLevel) InferIndicatorsFromCast(Cast cast)
    {
        var types = new List<WeatherType>();
        double? rain = null;

        var dayW = (cast.DayWeather ?? string.Empty).Trim();
        var nightW = (cast.NightWeather ?? string.Empty).Trim();
        var joined = string.Join(";", new[] { dayW, nightW });

        double? MapRain(string s)
        {
            if (string.IsNullOrEmpty(s)) return null;
            if (s.Contains("特大暴雨")) return 250;
            if (s.Contains("大暴雨")) return 100;
            if (s.Contains("暴雨")) return 50;
            if (s.Contains("大雨")) return 25;
            if (s.Contains("中雨")) return 10;
            if (s.Contains("小雨")) return 2;
            if (s.Contains("阵雨") || s.Contains("雷阵雨")) return 5;
            return null;
        }

        double? r1 = MapRain(dayW);
        double? r2 = MapRain(nightW);
        rain = MaxNullable(r1, r2);
        if (rain.HasValue && rain.Value >= 25)
        {
            types.Add(WeatherType.HeavyRain);
        }
        if (joined.Contains("雪")) types.Add(WeatherType.Snow);
        if (joined.Contains("雾")) types.Add(WeatherType.Fog);
        if (joined.Contains("雷") || joined.Contains("雷暴") || joined.Contains("大风")) types.Add(WeatherType.Storm);

        var wind = ParseWindLevel(cast.DayPower, cast.NightPower);
        if (wind.HasValue && wind.Value >= 6) types.Add(WeatherType.Wind);

        return (types, rain, wind);
    }

    // AMap Models
    private record ForecastItem(string City, string Adcode, string Province, string ReportTime, Cast[] Casts);
    private record Cast(string Date, string Week, string DayWeather, string NightWeather, string DayTemp, string NightTemp, string DayWind, string NightWind, string DayPower, string NightPower);
    private record AmapRegeoResponse(string Status, System.Text.Json.JsonElement? RegeoCode);
    private record AmapForecastResponse(string Status, ForecastItem[]? Forecasts);

    private static string? ExtractAdcode(System.Text.Json.JsonElement? regeo)
    {
        if (regeo is null) return null;
        var elem = regeo.Value;
        if (elem.ValueKind == System.Text.Json.JsonValueKind.Object)
        {
            if (elem.TryGetProperty("addressComponent", out var address))
            {
                return ExtractAdcodeFromAddress(address);
            }
        }
        else if (elem.ValueKind == System.Text.Json.JsonValueKind.Array)
        {
            foreach (var item in elem.EnumerateArray())
            {
                var code = ExtractAdcode(new System.Text.Json.JsonElement?(item));
                if (!string.IsNullOrWhiteSpace(code)) return code;
            }
        }
        return null;
    }

    private static string? ExtractAdcodeFromAddress(System.Text.Json.JsonElement address)
    {
        if (address.ValueKind == System.Text.Json.JsonValueKind.Object)
        {
            if (address.TryGetProperty("adcode", out var ad))
            {
                if (ad.ValueKind == System.Text.Json.JsonValueKind.String) return ad.GetString();
                if (ad.ValueKind == System.Text.Json.JsonValueKind.Array)
                {
                    foreach (var a in ad.EnumerateArray())
                    {
                        if (a.ValueKind == System.Text.Json.JsonValueKind.String) return a.GetString();
                    }
                }
            }
        }
        else if (address.ValueKind == System.Text.Json.JsonValueKind.Array)
        {
            foreach (var item in address.EnumerateArray())
            {
                var code = ExtractAdcodeFromAddress(item);
                if (!string.IsNullOrWhiteSpace(code)) return code;
            }
        }
        return null;
    }
}
